package rmioverlay.client;

import rmioverlay.lib.users.User;
import rmioverlay.server.ServerAppInterface;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.io.*;

@SuppressWarnings("serial")
public abstract class ClientApp<T extends ServerAppInterface> extends UnicastRemoteObject implements ClientAppInterface<T> {
	
	/**
	 * The server application reference to call remote methods
	 */
	private T serverApp;
	private boolean connected;
	
	protected ClientApp()
			throws RemoteException {
		super();
		this.serverApp = null;
		this.connected = false;
	}

	/**
	 * Connects and register to the stored server reference
	 * @throws RemoteException when a network issue occurs
	 * @throws RmiOverlayException when server reference is null
	 */
	public void connect()
			throws RemoteException {
		try {
			this.getServer().addClient(this);
			this.connected = true;
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}
	
	public boolean connect(String pLogin, String pPass)
			throws RemoteException {
		this.connect();
		
		return this.getServer().login(this, pLogin, pPass);
	}

	/**
	 * Disconnects from the stored server reference
	 * @throws RemoteException when a network issue occurs
	 */
	public void disconnect()
			throws RemoteException {
		try {
			this.getServer().removeClient(this);
			this.connected = false;
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Stores the ServerApp object reference for future use.
	 * @param pServerApp the reference to store
	 * @throws RemoteException when a network issue occurs 
	 */
	@SuppressWarnings("unchecked")
	protected void setServer(ServerAppInterface pServerApp)
			throws RemoteException {
		this.serverApp = (T) pServerApp;
	}
	
	/**
	 * Provides the stored reference of running ServerApp
	 * @return the reference of the running ServerApp.
	 * @throws RemoteException when a network issue occurs
	 */
	public T getServer()
			throws RemoteException {
		return this.serverApp;
	}
	
	/**
	 * Tells if client is connected to the server
	 * @return true if client is connected to server, false otherwise
	 */
	public boolean isConnected() { return this.connected; }

	/**
	 * Sents data to all connected clients
	 * @param <T> the type of data which will be sent
	 * @param pData the data to send
	 * @throws RemoteException if a network issue occurs
	 * @throws FileNotFoundException 
	 */
	public <U>void sendAll(U pData)
			throws RemoteException, FileNotFoundException {
		if (pData instanceof File)
			this.sendAll((File) pData);
		else
			this.getServer().<U>sendAll(pData);
	}
	
	/**
	 * Sents a file to all connected clients
	 * @param f the file to send
	 * @throws RemoteException if a network issue occurs
	 */
	public void sendAll(File f)
			throws RemoteException, FileNotFoundException {
		
		FileInputStream fi = new FileInputStream(f);
		byte[] byteArray  = new byte[(int) f.length()];
		
		try {
			fi.read(byteArray);
			fi.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.getServer().sendAll(byteArray, f.getName());
	}
	
	/**
	 * Sends data to a connected client
	 * @param <T> the type of data which will be sent
	 * @param pData the data to send
	 * @throws RemoteException if a network issue occurs
	 * @throws FileNotFoundException 
	 */
	public <U>void send(String pLogin, U pData)
			throws RemoteException, FileNotFoundException {
		if (pData instanceof File)
			this.send(pLogin, (File) pData);
		else
			this.getServer().<U>send(pLogin, pData);
	}
	
	/**
	 * Converts a file into an Array of bytes
	 * and sents a file to a connected clients
	 * @param f the file to send
	 * @throws RemoteException if a network issue occurs
	 */
	public void send(String pLogin, File f)
			throws RemoteException, FileNotFoundException {
		
		FileInputStream fi = new FileInputStream(f);
		byte[] byteArray  = new byte[(int) f.length()];
		
		try {
			fi.read(byteArray);
			fi.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.serverApp.send(pLogin, byteArray, f.getName());
	}

	/**
	 * Callback method, defines what happens when receiving data.
	 * @param pData the received data
	 * @throws RemoteException if a network issue occurs
	 */
	public abstract <U>void receive(U pData)
			throws RemoteException;
	
	/**
	 * Callback method, defines what happens when receiving a byte array.
	 * @param pData the received byte array
	 * @throws RemoteException if a network issue occurs
	 */
	public void receive(byte[] pData, String pFilename)
			throws RemoteException {
		String[] tabFilename = pFilename.split("\\.");
		//Getting file name
		String prefix = "";
		for(int i=0 ; i<tabFilename.length-1 ; i++)
			prefix += tabFilename[i];
		
		//Getting file ext
		String suffix = tabFilename[tabFilename.length-1];
		try {
			//Creating a file in temp directory
			File f = File.createTempFile(prefix, "."+suffix);
			
			//Saving file
			FileOutputStream fo = new FileOutputStream(f);
			fo.write(pData);
			
			this.<File>receive(f);
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}